package cc.eddic.practice.spring.web;

import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.TypeMismatchException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;

@Slf4j
@ControllerAdvice
@ResponseBody
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(NoSuchElementException.class)
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    public RestError noSuchElementException(HttpServletRequest request, NoSuchElementException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(EmptyResultDataAccessException.class)
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    public RestError emptyResultDataAccessException(HttpServletRequest request, EmptyResultDataAccessException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseStatus(value = HttpStatus.CONFLICT)
    public RestError dataIntegrityViolationException(HttpServletRequest request, DataIntegrityViolationException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(HttpMessageConversionException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError httpMessageConversionException(HttpServletRequest request, HttpMessageConversionException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError illegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(IllegalStateException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError illegalStateException(HttpServletRequest request, IllegalStateException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(RestClientException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError restClientException(HttpServletRequest request, RestClientException ex) {
        val str = ((HttpClientErrorException.BadRequest) ex).getResponseBodyAsString();
        log.warn(str, ex);
        return RestError.of(request, str);
    }

    @ExceptionHandler(BindException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError bindException(HttpServletRequest request, BindException ex) {
        return RestError.of(request, "BindException", buildFieldErrors(ex.getBindingResult()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {
        return RestError.of(request, "MethodArgumentNotValidException", buildFieldErrors(ex.getBindingResult()));
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError missingServletRequestParameterException(HttpServletRequest request, MissingServletRequestParameterException ex) {
        return RestError.of(request, ex);
    }

    @ExceptionHandler(TypeMismatchException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public RestError typeMismatchException(HttpServletRequest request, TypeMismatchException ex) {
        return RestError.of(request, ex);
    }

    private List<FieldError> buildFieldErrors(BindingResult result) {
        // https://juejin.cn/post/6844903649324302344
        List<FieldError> errors = new ArrayList<>();
        for (val err : result.getAllErrors()) {
            if (err instanceof org.springframework.validation.FieldError) {
                val fe = (org.springframework.validation.FieldError) err;

                FieldError fieldError = errors.stream()
                        .filter(x -> Objects.equals(x.getField(), fe.getField()))
                        .findFirst()
                        .orElseGet(() -> new FieldError(fe.getField()));
                if (fieldError.getErrors().isEmpty()) {
                    errors.add(fieldError);
                }

                fieldError.add(fe.getDefaultMessage());
            }
        }
        return errors;
    }
}
